requestLayout源码分析

invalidate是用来进行view的重绘的,它一般会导致onDraw的调用(对于ViewGroup容器来说它并不一定会调用onDraw)以使View改变自身内容,但是如果当view的大小尺寸发生了变化,此时就需要requestLayout对view进行布局请求。比如当view设置了布局参数后就需要进行布局请求。但需要注意的是requestLayout并不保证onDraw会调用,它只负责完成布局请求,调不调用onDraw取决于view的内容是否改变。所以一般情况下requestLayout和invalidate是结合着使用的。

1
2
3
4
5
6
public void setLayoutParams(ViewGroup.LayoutParams params) {
……
mLayoutParams = params;
……
requestLayout();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// /frameworks/base/core/java/android/view/View.java
public void requestLayout() {
if (mMeasureCache != null) mMeasureCache.clear();

if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
// Only trigger request-during-layout logic if this is the view requesting it,
// not the views in its parent hierarchy
ViewRootImpl viewRoot = getViewRootImpl();
if (viewRoot != null && viewRoot.isInLayout()) {//如果正在layout 发送请求layout
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
mAttachInfo.mViewRequestingLayout = this;
}
//设置FLAG_FORCE_LAYOUT 和 PFLAG_INVALIDATED标记
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;

if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}

requestLayout是从View开始,它首先判断的当前viewRoot是否正在进行layout,如果是,则发送请求给ViewRoot,告诉它当前view需要进行layout。否则为view打上PFLAG_FORCE_LAYOUT和PFLAG_INVALIDATED标记,然后调用parent的requestLayout,这里parent即它的父view,可以是ViewGroup也可以是ViewRoot,其中ViewGroup没有覆盖requestLayout,那么依然是调用View的requestLayout。这实际上是一个递归调用为父view打上请求布局的标记。直到ViewRootImpl。

1
2
3
4
5
6
7
8
9
///frameworks/base/core/java/android/view/ViewRootImpl.java
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {//如果layout请求没有在执行
checkThread();
mLayoutRequested = true;//设置layout请求标记
scheduleTraversals();
}
}

ViewRootImpl的requestLayout中设置mLayoutRequested为true,然后开启请求布局。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
private void performTraversals() {
……
boolean layoutRequested = mLayoutRequested && !mStopped;
……
if (!mStopped) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged) {

//获得view宽高的测量规格,lp.width和lp.height表示DecorView根布局宽和高
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//开始执行测量操作

……
}
}


final boolean didLayout = layoutRequested && !mStopped;//layoutRequested 为true并且actitivy并非暂停则需要执行layout
boolean triggerGlobalLayoutListener = didLayout
|| attachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
……

}
……
boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw() ||
viewVisibility != View.VISIBLE;
if (!cancelDraw && !newSurface) {//既没有取消绘制,也没有创建新的平面
if (!skipDraw || mReportNextDraw) {//
……
performDraw();//开始执行绘制操作
}
}
……
}

mLayoutRequested 在requestLayout中设置为true,mStopped表示当前view树所在的window状态不是停止的,一般为false,那么layoutRequested为true,didLayout为true。那么分别执行performMeasure和performLayout,下面我们分别分析。

1
2
3
4
5
6
7
8
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}

ViewRootImpl的performMeasure执行view的measure方法进行测量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
……
if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {

// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;//清除meaure set 标记 用来检测是否调用了setMeasuredDimension

……

mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}

mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;

mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}

由于mPrivateFlags在requestLayout中设置了PFLAG_FORCE_LAYOUT,所有清除PFLAG_MEASURED_DIMENSION_SET的标记,这个标记用于检测是否调用setMeasuredDimension,同时打上PFLAG_LAYOUT_REQUIRED标记,表示请求布局。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
mLayoutRequested = false;
...
mInLayout = true;
final View host = mView;
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mInLayout = false;
int numViewsRequestingLayout = mLayoutRequesters.size();
if (numViewsRequestingLayout > 0) {
ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
false);
if (validLayoutRequesters != null) {
mHandlingLayoutInLayoutRequest = true;//表示正在进行layout请求操作

// Process fresh layout requests, then measure and layout
int numValidRequests = validLayoutRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = validLayoutRequesters.get(i);
view.requestLayout();
}
measureHierarchy(host, lp, mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight);
mInLayout = true;
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

mHandlingLayoutInLayoutRequest = false;
……
}
}
}
……
}

performLayout负责执行viewlayout过程,同时对请求布局的view也执行requstLayout。这里host就是view树的根节点,即DecorView,它是个FrameLayout。所以我们看看ViewGroup的layout。

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public final void layout(int l, int t, int r, int b) {
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {
mTransition.layoutChange(this);
}
super.layout(l, t, r, b);
} else {
// record the fact that we noop'd it; request layout when transition finishes
mLayoutCalledWhileSuppressed = true;
}
}

viewGroup调用view的layout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void layout(int l, int t, int r, int b) {
……
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;

boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);//setFrame中可能会触发invalidate进行重绘置

if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {//如果布局发生了变化 则还需要对子视图进行重新布局
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

……
}

mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}

由于在measure过程中设置了PFLAG_LAYOUT_REQUIRED标记,那么就会调用onLayout来进行view的布局过程,这个过程完成后,清理PFLAG_LAYOUT_REQUIRED和PFLAG_FORCE_LAYOUT标记表示布局过程完成了。这里需要注意的是view在测量后大小可能发生变化,这时候通过setFrame设置其边框时会调用invalidate的调用,因此可能会导致onDraw的调用。

坚持原创技术分享,您的支持将鼓励我继续创作!